﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;

namespace CodeAngel.Domain.Framework
{
    #region Entity

    /// <summary>
    /// Base class for entity classes.
    /// </summary>
    public abstract class Entity : INotifyPropertyChanged, IAggregateRoot //, INotifyDataErrorInfo 
    {
        #region Constructors

        protected Entity(Aggregate aggregate)
            : this(aggregate, null) { }

        protected Entity(Aggregate aggregate, Entity parentEntity)
        {
            this.Aggregate = aggregate;
            this.Aggregate.BuildObjectGraphCompleted += delegate { this.BuildObjectGraphCompleted(); };
            this.ParentEntity = parentEntity;
            this.Properties = new PropertyCollection();
            this.ValidationRules = new RuleCollection();
        }

        #endregion

        #region ID

        /// <summary>
        /// Uniquely identifies this entity.
        /// </summary>
        public abstract Guid ID { get; }

        #endregion

        #region Equals

        public override bool Equals(object entity)
        {
            if (entity is Entity)
                return this.ID.Equals((entity as Entity).ID);
            else
                return false;
        }

        #endregion

        #region Properties

        /// <summary>
        /// A collection of this entity's properties.
        /// </summary>
        internal PropertyCollection Properties { get; private set; }

        /// <summary>
        /// Asks the derived class to initialize all of their properties.
        /// </summary>
        internal abstract void InitializeAllProperties();

        /// <summary>
        /// Asks the derived class to set the properties of a new entity to thier default values.
        /// </summary>
        internal abstract void SetDefaultPropertyValues();

        #endregion

        #region Aggregate

        /// <summary>
        /// Gets the aggregate that this entity belongs to.
        /// </summary>
        public Aggregate Aggregate { get; private set; }

        #endregion

        #region ParentEntity

        /// <summary>
        /// The parent entity of this entity, if applicable.
        /// </summary>
        /// <remarks>This will be null if this entity is the root of an aggregate.</remarks>
        public Entity ParentEntity { get; set; }

        #endregion

        #region SynchronizeDto

        ///// <summary>
        ///// Synchronizes the identified dto with any data changes made to this entity.
        ///// </summary>
        ///// <remarks>If the passed in dto is not a valid type, call the ThrowInvalidDtoTypeException 
        ///// method.</remarks>
        //internal abstract void SynchronizeDto(IDto thisDto);

        /// <summary>
        /// Synchronizes the identified list of DTOs with any changes made to this entity (i.e., if
        /// this entity has been modified, the corresponding DTO will be located in the list and its
        /// properties will be updated; if this is a new entity, a new DTO will be created, its 
        /// properties set and added to the list; if flagged as deleted, then the matching DTO in the
        /// list will have its state set to deleted.
        /// </summary>
        //internal abstract void SynchronizeDto(List<IDto> theseDtos);

        ///// <summary>
        ///// Gets a new dto with its properties synchronized with the current state of this entity.
        ///// </summary>
        //internal abstract IDto GetNewDto();


        protected void ThrowInvalidDtoTypeException(string expectedType, string receivedType)
        {
            throw new InvalidDtoTypeException(expectedType, receivedType);
        }


        #endregion

        #region Delete

        /// <summary>
        /// Flags this entity as deleted.  Nothing is actually deleted until the Aggregate this
        /// entity belongs to is Saved.
        /// </summary>
        public void Delete()
        {
            this.FlaggedAsDeleted = true;
        }

        /// <summary>
        /// Gets or sets if this entity is flagged as deleted.
        /// </summary>
        /// <remarks>An entity flagged as deleted will be deleted when the parent aggregate is saved.</remarks>
        public bool FlaggedAsDeleted 
        {
            get { return this._FlaggedAsDeleted; }
            set
            {
                if (value != this._FlaggedAsDeleted)
                {
                    this._FlaggedAsDeleted = value;
                    this.RaisePropertyChanged("FlaggedAsDeleted");
                }
            }
        }
        private bool _FlaggedAsDeleted = false;

        #endregion

        #region Validation

        ///// <summary>
        ///// Determines if this entity is currently valid, including all of its properties (including
        ///// any object graph below this entity).
        ///// </summary>
        //public bool IsValid()
        //{
        //    if (!this.ValidationRules.IsValid()) return false;

        //    // Check all our properties except our parent entity.
        //    foreach (var ThisProperty in this.Properties)
        //    {
        //        if (!this.PropertyRepresentsParentEntity(ThisProperty))
        //        {
        //            if (!ThisProperty.IsValid()) return false;
        //        }
        //    }
        //    return true;
        //}
        /// <summary>
        /// Determines if this entity is currently valid.
        /// </summary>
        public bool IsValid()
        {
            if (!this.ValidationRules.IsValid()) return false;

            //// Check all our properties except our parent entity.
            //foreach (var ThisProperty in this.Properties)
            //{

            //    if (!this.IsAnEntityProperty(ThisProperty))
            //    {
            //        if (!ThisProperty.IsValid()) return false;
            //    }
            //}
            return true;
        }

        /// <summary>
        /// Determines if the identified property is a link back to our parent entity.
        /// </summary>
        private bool PropertyRepresentsParentEntity(IProperty property)
        {
            if (property is IValueProperty)
            {
                var PropertyAsValueProperty = property as IValueProperty;
                return PropertyAsValueProperty.GetValue() == this.ParentEntity;
            }

            return false;
        }

        /// <summary>
        /// Determines if the identified property is an entity property.
        /// </summary>
        private bool IsAnEntityProperty(IProperty property)
        {
            if (property is IValueProperty)
            {
                var PropertyAsValueProperty = property as IValueProperty;
                return PropertyAsValueProperty.GetValue() as Entity != null;
            }

            return false;
        }

        /// <summary>
        /// Identifies the rules at the entity level that must be valid in order for this entity
        /// to be considered in a valid state.
        /// </summary>
        /// <remarks>
        /// Note that more often than not, validation rules are at the individual property level.  This 
        /// collection does not hold the rules for those properties.  This typically only holds rules that 
        /// apply across multiple properties of this entity.
        /// </remarks>
        public RuleCollection ValidationRules { get; private set; }


        public RuleCollection GetInvalidRules()
        {
            var InvalidRules = new RuleCollection();
            InvalidRules.AddRange(this.ValidationRules.GetInvalidRules());

            foreach (var ThisProperty in this.Properties)
            {
                InvalidRules.AddRange(ThisProperty.GetInvalidRules());
                if (ThisProperty is IValueProperty)
                {
                    var Value = (ThisProperty as IValueProperty).GetValue();
                    if (Value != null && Value is Entity)
                    {
                        var ValueAsEntity = Value as Entity;
                        if(ValueAsEntity != this.ParentEntity)
                            InvalidRules.AddRange(ValueAsEntity.GetInvalidRules());
                    }
                }
                else if (ThisProperty is IEntityCollectionProperty)
                {
                    var AsEntityCollectionProperty = ThisProperty as IEntityCollectionProperty;
                    //InvalidRules.AddRange(AsEntityCollectionProperty.GetInvalidRules());
                    InvalidRules.AddRange(AsEntityCollectionProperty.GetValue().GetInvalidRules());
                    //foreach (Entity ChildEntity in AsEntityCollectionProperty.GetValue())
                    //    InvalidRules.AddRange(ChildEntity.GetInvalidRules());
                }
            }

            return InvalidRules;
        }

        #endregion

        #region ThrowUninitializedPropertyException

        protected void ThrowUninitializedPropertyException(string propertyName)
        {
            throw new UninitializedPropertyException(propertyName, this.Aggregate);
        }

        #endregion

        #region ThrowPropertyIsNullException

        protected void ThrowPropertyIsNullException(IProperty thisProperty)
        {
            throw new PropertyIsNullException(thisProperty);
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        internal void RaisePropertyChanged(string propertyName)
        { 
            if(this.PropertyChanged != null) this.PropertyChanged(this,new PropertyChangedEventArgs(propertyName)); 
        }

        #endregion

        #region BuildObjectGraphCompleted

        //public void NotifyBuildObjectGraphCompleted()
        //{
        //    this.BuildObjectGraphCompleted();
        //}

        internal abstract void BuildObjectGraphCompleted();

        #endregion

        #region IsNew

        /// <summary>
        /// Gets or sets the IsNew.
        /// </summary>
        public bool IsNew
        {
            get { return this._IsNew; }
            internal set
            {
                if (value != this._IsNew)
                {
                    this._IsNew = value;
                    this.RaisePropertyChanged("IsNew");
                }
            }
        }
        private bool _IsNew = false;

        #endregion

        #region IsVisible

        /// <summary>
        /// Gets or sets the IsVisible.
        /// </summary>
        public bool IsVisible
        {
            get { return this._IsVisible; }
            set
            {
                if (value != this._IsVisible)
                {
                    this._IsVisible = value;
                    this.RaisePropertyChanged("IsVisible");
                }
            }
        }
        private bool _IsVisible = true;

        #endregion

        #region INotifyDataErrorInfo

        //public string Error
        //{
        //    get { return string.Empty; }
        //}

        //public string this[string propertyName]
        //{
        //    get 
        //    { 
        //        // find the property
        //        foreach (var ThisProperty in this.Properties)
        //        {
        //            if (ThisProperty.Name == propertyName)
        //            {
        //                if (!ThisProperty.IsValid()) return ThisProperty.GetInvalidRules().ToViolationString();
        //                return string.Empty;
        //            }
        //        }

        //        return string.Empty;
        //    }
        //}




        //public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public IEnumerable GetErrors(string propertyName)
        {
            //throw new NotImplementedException();
            return null;
        }

        public bool HasErrors
        {
            get 
            { 
                //throw new NotImplementedException(); 
                return false;
            }
        }

        #endregion

    }

    #endregion

    #region IEntityCollection

    public interface IEntityCollection : IEnumerable
    {
        RuleCollection GetInvalidRules();
    }

    #endregion

    #region EntityCollection

    public class EntityCollection<TEntity> : ObservableCollection<TEntity>, IEntityCollection, INotifyPropertyChanged, IAggregateRoot where TEntity : Entity
    {
        #region Constructors

        protected EntityCollection(Aggregate aggregate)
            : this(aggregate, null)
        { }

        protected EntityCollection(Aggregate aggregate, Entity parentEntity)
        {
            this.Aggregate = aggregate;
            this.ParentEntity = parentEntity;
            base.CollectionChanged += this.Base_CollectionChanged;

            this.ValidationRules = new RuleCollection();
        }

        #endregion

        #region Base_CollectionChanged
        
        private void Base_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                // Link the parent entity of this collection to the added entity.  Thus every 
                // entity in this collection will have the same parent entity.
                foreach (Entity ThisEntity in e.NewItems)
                    ThisEntity.ParentEntity = this.ParentEntity;
            }
        }

        #endregion

        #region ParentEntity

        public Entity ParentEntity { get; set; }

        #endregion
        
        #region Aggregate

        /// <summary>
        /// Gets the aggregate that this collection belongs to.
        /// </summary>
        public Aggregate Aggregate { get; private set; }

        #endregion

        #region Validation

        /// <summary>
        /// Determines if the validation rules for this entity collection are valid, and if all the entities in
        /// this collection are also valid.
        /// </summary>
        public bool IsValid()
        {
            if (!this.ValidationRules.IsValid()) return false;

            foreach (var ChildEntity in this)
                if (!ChildEntity.IsValid()) return false;

            return true;
        }


        /// <summary>
        /// Gets the rules that apply to this collection that must be satisfied, at minimum,
        /// for this collection to be saved.  (To actually be saveable, all entities in this
        /// collection must also be in an IsValid = true state.)
        /// </summary>
        public RuleCollection ValidationRules { get; private set; }


        /// <summary>
        /// Gets any invalid rules
        /// </summary>
        public RuleCollection GetInvalidRules()
        {
            var InvalidRules = new RuleCollection();
            InvalidRules.AddRange(this.ValidationRules.GetInvalidRules());

            foreach (var ChildEntity in this)
                InvalidRules.AddRange(ChildEntity.GetInvalidRules());

            return InvalidRules;
        }

        #endregion

        #region Find

        /// <summary>
        /// Finds and returns the entity in this collection with the identified ID.  If no matching entity is found, 
        /// null is returned.
        /// </summary>
        public TEntity Find(Guid id)
        {
            foreach (var ThisEntity in this)
                if (ThisEntity.ID == id) return ThisEntity;
            return null;
        }

        #endregion

        #region INotifyPropertyChanged Members

        public new event PropertyChangedEventHandler PropertyChanged;
        internal void RaisePropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region IEntityCollection Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return base.GetEnumerator();
        }

        #endregion


    }

    #endregion

    #region InvalidDtoTypeException

    /// <summary>
    /// Identifies that a specific IDto was not of an expected type.
    /// </summary>
    public class InvalidDtoTypeException : Exception
    {

        internal InvalidDtoTypeException(string expectedType, string receivedType)
            : base("Invalid DTO type.  Was expecting " + expectedType + ", but received " + receivedType)
        {
            this.ExpectedType = expectedType;
            this.ReceivedType = receivedType;
        }

        public string ExpectedType { get; private set; }
        public string ReceivedType { get; private set; }

    }

    #endregion

}
